/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <string>

#include "debughelper/debughelper.h"
#include "dtime/dtime.h"
#include "logger/logger.h"
#include "datachannel/datachannel.h"
#include "datachannel/datachannelfactory.h"
#include "thread/mutex.h"
#include "nmea0183/nmea0183message.h"
#include "nmea0183/nmea0183gga.h"
#include "nmea0183/nmea0183rmc.h"
#include "nmea0183/nmea0183parser.h"
#include "nmeastatus.h"
#include "nmeainterface.h"


using namespace nmeajgp;


// pthread to start nmea location updates
void *nmeajgp::nmea_start_pthread(void *arg) {
    NmeaInterface *object = reinterpret_cast<NmeaInterface *> (arg);
    if (object != NULL) {
        object->run();
        object->log->debug("nmea_start_pthread returning ...");
    }
    return arg;
}


NmeaInterface::NmeaInterface(logger::Logger *logger) : log(logger) {
    channel             = NULL;
    thread_ID.p         = NULL;
    thread_ID.x         = 0;
    status_mutex        = new thread::Mutex();
    status              = new NmeaStatus();
    parser              = new nmea0183::Nmea0183Parser();
    maxBufferLen        = 1024;
    buffer              = new char[maxBufferLen];

    stop_flag           = false;
}

NmeaInterface::~NmeaInterface() {
    stop();

    // wait for nmea reader thread to stop
    pthread_join(thread_ID, NULL);
    dtime::delaySec(1.0);
    log->debug("~NmeaInterface exiting ...");

    delete channel;
    channel = NULL;

    delete []buffer;
    buffer = NULL;

    delete parser;
    parser = NULL;

    delete status;
    status = NULL;

    delete status_mutex;
    status_mutex = NULL;
}

bool NmeaInterface::init(const std::string &descriptor) {
    bool flag = false;

    if ((status == NULL)) {
        log->fatal("NmeaInterface.init: All objects not created");
    } else {
        channel = datachannel::DataChannelFactory::getInstance(descriptor, log);
        flag = (channel != NULL);
    }

    return flag;
}

bool NmeaInterface::start() {
    bool flag = false;

    // create thread for nmea location updates
    int rc = pthread_create(&thread_ID, NULL, nmea_start_pthread, this);
    if (rc == 0) {
        log->info("NmeaInterface started");
        flag = true;
    } else {
        log->fatal("NmeaInterface.start: pthread_create failed: %d", rc);
    }

    return flag;
}

void NmeaInterface::run() {
    // loop
    while (!stop_flag) {
        dtime::delaySec(0.1);  // TODO(me): make smarter
        if (!isStop()) {
            int n = channel->read(buffer, maxBufferLen);
            if (n > 0) {
                process(static_cast<char *> (buffer), n);
            }
        }
    }
}

void NmeaInterface::stop() {
    if (!stop_flag) {
        stop_flag = true;
        log->debug("NmeaInterface.stop stopping ...");
    }
}

bool NmeaInterface::isStop() const {
    return stop_flag;
}

void NmeaInterface::getStaus(NmeaStatus *dst_status) {
    if ((dst_status != NULL) && !isStop()) {
        thread::Mutex_scopedLock msl(status_mutex);
        *dst_status = *status;
    }
}

void NmeaInterface::process(const char *data, int len) {
    if (parser->parse(data, len)) {
        bool flag = true;
        do {
            nmea0183::Nmea0183Message *msg = parser->getNextMessage();
            if (msg == NULL) {
                flag = false;
            } else {
                if (msg->isMessageType("GGA")) {
                    nmea0183::Nmea0183GGA *gga =
                        static_cast<nmea0183::Nmea0183GGA *> (msg);
                    log->trace("GGA");

                    // update status from GGA message
                    thread::Mutex_scopedLock msl(status_mutex);
                    status->time_s              = gga->time_s;
                    status->latitude_degN       = gga->latitude_degN;
                    status->longitude_degE      = gga->longitude_degE;
                    status->altitude_m          = gga->altitude_m;
                } else if (msg->isMessageType("RMC")) {
                    nmea0183::Nmea0183RMC *rmc =
                        static_cast<nmea0183::Nmea0183RMC *> (msg);
                    log->trace("RMC");

                    // update status from RMC message
                    thread::Mutex_scopedLock msl(status_mutex);
                    status->time_s              = rmc->time_s;
                    status->latitude_degN       = rmc->latitude_degN;
                    status->longitude_degE      = rmc->longitude_degE;
                    status->course_rad          = rmc->course_rad;
                    status->speed_mps           = rmc->speed_mps;
                }
                parser->deleteMessage(msg);
                msg = NULL;
            }
        } while (flag);
    }
}
